个人博客

学习加油站


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

浅谈 var、let 和 const (三)

发表于 2019-08-16

浅谈 var、let 和 const (三)

前言

在前一篇文章中,我们讲到了 var、let 和 const 有关的变量提升和函数提升。

声明方式 变量提升 暂时性死区 重复声明 初始值 作用域
var 允许 不存在 允许 不需要 除块级
let 不允许 存在 不允许 不需要 块级
const 不允许 存在 不允许 需要 块级

今天,我们来学习下 var、let 和 const的其他区别点:暂时性死区,重复声明和初始值

1. 暂时性死区

(1) 什么是暂时性死区

ES6 明确规定,如果区块中存在 let 和 const 命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。这在语法上,称为“暂时性死区”(Temporal Dead Zone,简称 TDZ)。

理解:只要块级作用域内存在 let 或 const 命令,它所声明的变量就绑定(binding)这个区域,不再受外部影响。当进入这个区域,let 或者const 声明的变量不能被访问(获取或设置)直到执行到达声明。

1
2
3
4
5
6
7
8
9
10
11
12
if (true) {   
// TDZ 开始
tmp = 'a'; // ReferenceError
console.log(tmp); // ReferenceError

let tmp;
// TDZ 结束
console.log(tmp); // undefined

tmp = 1;
console.log(tmp); // 1
}

代码理解:在块级作用域内,一开始存在全局变量 tmp。但是,接下来的 let 声明了一个局部变量 tmp,导致后者绑定了这个块级作用域。所以在 let 声明变量前,对 tmp 赋值会报错(ReferenceError)。

本质:只要一进入块级作用域,所要使用的变量 tmp 就已经存在了,但是不可获取。只有等到声明变量的那一行代码出现,才可以获取和使用该变量。

(2) typeof 陷阱
  • 当一个变量没有被声明时,使用 typeof 是不会报错的
1
typeof undeclared_variable 		// "undefined"

undeclared_variable 是一个不存在的变量名,结果返回“undefined”

  • “暂时性死区” 意味着 typeof 不再是一个百分之百安全的操作。
1
2
typeof x; // 报错! ReferenceError
let x;

变量 x 使用 let 命令声明,所以在声明之前,都属于 x 的“暂时性死区”,只要用到该变量就会报错。

因此,typeof 运行时就会抛出一个ReferenceError

总结: typeof运算符并非是百分之百安全的,因此我们要养成良好的编程习惯,变量一定要在声明之后使用,否则就报错

(3) 其他例子

实际上,有些 “死区” 比较隐蔽,不太容易发现。

1
2
3
4
function fun(x = y, y = 2) {
return [x, y];
}
fun(); // 报错

上面代码中,调用 fun 函数之所以报错(某些实现 可能不会报错),是因为参数 x 默认值等于另一个参数 y ,而此时 y 还没有声明,属于”死区“。

如果 y 的默认值是 x ,就不会报错,因为此时 x 已经声明了。

1
2
3
4
function fun(x = 2, y = x) {
return [x, y];
}
fun(); // [2, 2]

此外,下面的代码也会报错,与 var 的行为不同。

1
2
3
4
5
6
// 不报错
var x = x;

// 报错
let x = x;
// ReferenceError

上面代码报错,也是因为暂时性死区。使用 let 声明变量时,只要变量在还没有声明完成前使用,就会报错。

上面这行就属于这个情况,在变量x的声明语句还没有执行完成前,就去取x的值,导致报错”x未定义“。

(4) 总结

ES6 规定暂时性死区和let、const语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。这样的错误在 ES5 是很常见的,现在有了这种规定,避免此类错误就很容易了。

2. 重复声明

定义:指在相同作用域内,重复声明同一个变量。

let 和 const 命令声明的变量不允许重复声明,而 var 可以重复声明。

在一个作用域中,已经用 var、let、cons t声明过某标识符之后,不能在用 let、const声明变量,不然会抛出错误。

1
2
3
4
5
6
7
8
let a = 1;
let a = 0; // 报错! Uncaught SyntaxError: Identifier 'a' has already been declared

const PI = 3.14
const PI = 3.1415 // 报错! Uncaught SyntaxError: Identifier 'a' has already been declared

var b = 1;
var b = 0;

在作用域中,嵌套一个作用域就不会报错了

1
2
3
4
5
var a = 1;
if (true) {
let a = 0;
}
// 不会报错

3. 初始值 和 const

let 和 var 声明变量时,可以不需要初始值,而 const 需要。

  • const 声明的是只读的常量,一旦声明,就必须立即初始化(赋值),否则会报错

  • const 声明之后 值不能改变。

1
2
3
const PI = 3.14;
PI = 3.1415;
// 报错! Uncaught TypeError: Assignment to constant variable.

如果 const 声明的是一个引用类型,则不能改变它的内存地址

本质:const 实际上保证的,并不是变量的值不得改动,而是变量指向的那个 内存地址所保存的数据 不得改动。

  • 对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。
  • 对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了

举例如下

1
2
3
4
5
6
7
8
const obj = {
a : '123'
}
obj.a = 'abc';
// 不会发生报错,只会改变值
obj = {};
// 由于改变了对象的指针,所以会发生报错
// Uncaught TypeError: Assignment to constant variable.

如果真的想将对象冻结,应该使用Object.freeze方法。

1
2
3
4
const foo = Object.freeze({}); 
// 常规模式时,下面一行不起作用;
// 严格模式时,该行会报错
foo.prop = 123;

补充:顶层对象的属性

let、const 和 class 命令声明的全局变量,不属于顶层对象的属性。

从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩。

全局作用域中,var 声明的变量关联到顶层对象的属性

1
2
3
4
5
6
7
var a = 1;
// 如果在 Node 的 REPL 环境,可以写成 global.a
// 或者采用通用方法,写成 this.a
window.a // 1

let b = 1;
window.b // undefined

参考文章 阮一峰 ECMAScript 6 入门

# JS
浅谈 var、let 和 const (二)
「ES6学习」解构赋值(一)
  • 文章目录
  • 站点概览

WYP

知识管理,自我管理
12 日志
8 标签
GitHub E-Mail 简书
  1. 1. 浅谈 var、let 和 const (三)
    1. 1.1. 前言
    2. 1.2. 1. 暂时性死区
      1. 1.2.0.1. (1) 什么是暂时性死区
      2. 1.2.0.2. (2) typeof 陷阱
      3. 1.2.0.3. (3) 其他例子
      4. 1.2.0.4. (4) 总结
  2. 1.3. 2. 重复声明
  3. 1.4. 3. 初始值 和 const
  4. 1.5. 补充:顶层对象的属性
© 2019 – 2021 wyp